#include "N1Date.h"

static const uint32 gSecondsInMin = 60;                       //60
static const uint32 gSecondsInHour = 60 * gSecondsInMin;      //3600
static const uint32 gSecondsInDay = gSecondsInHour * 24;      //86400
static const uint32 gSecondsInYear = gSecondsInDay * 365;     //31536000
static const uint32 gSecondsInLeapYear = gSecondsInDay * 366; //31622400
static const uint32 gLeapYearsInCentury = 24;                 //25-1
static const uint32 gCommonYearsInCentury = 100 - 24;         //76
static const uint32 gSecondsInCentury = gLeapYearsInCentury * gSecondsInLeapYear 
                                      + gCommonYearsInCentury * gSecondsInYear;
static const uint32 gSecondsInFourYears = gSecondsInLeapYear + 3 * gSecondsInYear;

static const short gMonthStart[2][13] =
{{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }};

static const char gMonthLength[2][13] =
{{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
 { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }};


N1NAME_DEF(N1Date)

N1Date::N1Date() 
{
    time_t t = time(NULL);
    m_time = *localtime( &t );
}

N1Date::N1Date(uint16 year, uint16 month, uint16 day, uint16 hour, uint16 min, uint16 sec)
{
    memset(&m_time, 0, sizeof(m_time));
    m_time.tm_year = year - 1900;
    m_time.tm_mon = month;
    m_time.tm_mday = day;
    m_time.tm_hour = hour;
    m_time.tm_min = min;
    m_time.tm_sec = sec;
    m_time.tm_yday = calculateDayOfYear(year, month, day);
    m_time.tm_wday = calculateDayOfWeek(year, month, day);
}

N1Date* N1Date::today()
{
    return (new N1Date())->autorelease<N1Date>();
}

uint16 N1Date::calculateDayOfYear(uint16 year, uint16 month, uint16 day)
{
    return gMonthStart[isLeapYear(year)?1:0][month] + day - 1;
}

uint16 N1Date::calculateDayOfWeek(uint16 year, uint16 month, uint16 day)
{
    int a = (14 - month) / 12;
    int y = year - a;
    int m = month + 12 * a - 2;
    return (7000 + (day + y + y / 4 - y / 100 + y / 400 + (31 * m) / 12)) % 7;
}

N1AbsoluteTime N1Date::absoluteTime() const
{
    N1AbsoluteTime t = 0;

    uint32 year = yearOfCommonEra();
    uint32 years = year - 1601;
    uint32 centuries = years / 100;
    uint32 leftYears = years - centuries * 100;
    uint32 fourYears = leftYears / 4;
    uint32 lastYears = leftYears - fourYears * 4;
    uint32 month = monthOfYear();
    uint32 daysInMonths = gMonthStart[isLeapYear(year)?1:0] [month];

    t = (N1AbsoluteTime)centuries * gSecondsInCentury + 
        (N1AbsoluteTime)fourYears * gSecondsInFourYears +
        (N1AbsoluteTime)lastYears * gSecondsInYear + 
        (N1AbsoluteTime)daysInMonths * gSecondsInDay +
        (N1AbsoluteTime)(dayOfMonth() - 1) * gSecondsInDay +
        hourOfDay() * gSecondsInHour + minuteOfHour() * gSecondsInMin + 
        secondOfMinute();

    return t;
}

N1Date* N1Date::dateWithAbsoluteTime(N1AbsoluteTime t)
{
    uint32 centuries = (uint32)(t / gSecondsInCentury);
    uint32 secondsAfterCentury = (uint32)(t - centuries * gSecondsInCentury);
    uint32 fourYearsAfterCentury = secondsAfterCentury / gSecondsInFourYears;
    uint32 secondsAfterFour = secondsAfterCentury - fourYearsAfterCentury * gSecondsInFourYears;
    uint32 yearsAfterFour = secondsAfterFour / gSecondsInYear;
    uint32 secondsInLastYear = secondsAfterFour - yearsAfterFour * gSecondsInYear;
    uint32 year = 1601 + centuries * 100 + fourYearsAfterCentury * 4 + yearsAfterFour;
    uint32 index = isLeapYear(year) ? 1 : 0;
    uint32 month = 0;
    while(++month)
    {
        if(secondsInLastYear < (gMonthStart[index][month] * gSecondsInDay))
        {
            month--;
            break;
        }
    }
    uint32 secondsAfterMonths = secondsInLastYear - (gMonthStart[index][month] * gSecondsInDay);
    uint32 day = secondsAfterMonths / gSecondsInDay;
    uint32 secondsInDay = secondsAfterMonths - day * gSecondsInDay;
    uint32 hour = secondsInDay / gSecondsInHour;
    uint32 secondsInHour = secondsInDay - hour * gSecondsInHour;
    uint32 min = secondsInHour / gSecondsInMin;
    uint32 seconds = secondsInHour - min * gSecondsInMin;

    return (new N1Date(year, month, day + 1, hour, min, seconds))->autorelease<N1Date>();
}

N1Date* N1Date::dateWithTimeIntervalSinceNow(N1TimeInterval secs)
{

    N1AbsoluteTime now = N1Date().absoluteTime();

    N1AbsoluteTime newTime = now + secs;
    
    return dateWithAbsoluteTime(newTime);
}

uint16 N1Date::yearOfCommonEra() const 
{
    return m_time.tm_year + 1900;
}

uint16 N1Date::monthOfYear() const 
{
    return m_time.tm_mon;
}

uint16 N1Date::dayOfMonth() const 
{
    return m_time.tm_mday;
}

uint16 N1Date::dayOfWeek() const 
{
    return m_time.tm_wday;
}

uint16 N1Date::dayOfYear() const 
{
    return m_time.tm_yday;
}

uint16 N1Date::hourOfDay() const 
{
    return m_time.tm_hour;
}

uint16 N1Date::minuteOfHour() const 
{
    return m_time.tm_min;
}

uint16 N1Date::secondOfMinute() const 
{
    return m_time.tm_sec;
}

bool N1Date::operator<(const N1Date& date) const 
{
    return absoluteTime() < date.absoluteTime();
}

#ifdef N1LIB_SELFTEST

N1TEST(N1DateToday)
{
    N1AutoreleasePool p;

    N1Date * d1 = N1Date::today();

    N1Date * d2 = N1Date::dateWithTimeIntervalSinceNow(10);

    N1TimeInterval ti = d2->absoluteTime() - d1->absoluteTime();

    N1CHECK(ti<11 && ti>5, 1);
}

#endif
